import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Grid, Table, Message } from '@alifd/next';
import axios from 'axios';
import moment from 'moment';
import { ApiOfGatewayDetail, UrlOfRealtimeMonitorData } from '#/utils/coreApiDefine'
import styles from '../styles/styles';
import './Topology.scss';

const { Row, Col } = Grid;
const d3 = require('d3');;
const height = 500, width = 500, nodeBaseWidth = 40, nodeBaseHeight = 40;
const gatewayIcon = require('./images/gateway.png');
const deviceIcon = require('./images/device.png');

const drag = simulation => {
  
  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  
  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  
  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }
  
  return d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);
}

export default class Topology extends Component {
  static displayName = 'Topology';

  constructor(props) {
    super(props);
    this.state = {
      report: []
    };
  }

  renderDeviceName(value, index, item) {
    const linkProps = {
      to: "/manage/device/" + item.code + '/operation'
    };
    return (
      <Link {...linkProps}>{item.name}</Link>
    );
  }

  renderExecutionCycle = (value, index, item) => {
    if(!value) {
      return (
        <span></span>
      )
    }
    let dur = value;
    if(item.executionCycle > 60) {
      dur = moment.duration(value, 'seconds').asMinutes() + ' 分'
    } else {
      dur = moment.duration(value, 'seconds').asSeconds() + ' 秒'
    }
    return (
      <span>{dur}</span>
    );
  }

  render() {
    return (
      <div style={styles.infoColumn}>
        <h5 style={styles.infoColumnTitle}>拓扑 & 工作状态</h5>
        <Row wrap>
          <Col l="24">
            <Table
              dataSource={this.state.report}
              hasBorder={false}
            >
              <Table.Column title="名称" dataIndex="name" cell={this.renderDeviceName}/>
              <Table.Column title="协议" dataIndex="protocol" />
              <Table.Column title="周期" dataIndex="executionCycle" cell={this.renderExecutionCycle} />
              <Table.Column title="指标数" dataIndex="metricSize" />
              <Table.Column title="合并后" dataIndex="willMergedSize" />
              <Table.Column title="上次时间" dataIndex="lastCollectedTime" />
              <Table.Column title="上次耗时" dataIndex="elapsedTime" />
            </Table>
            <Message title="说明" type="help">
              同一网关下的Modbus设备，以串行方式顺序采集。所以应该综合考虑采集周期、设备数量、合并后指标数量三个因素，否则会发生采集端任务数过多，逐渐压死采集服务。<br />
              采集耗时包括：服务调用网络耗时、指标解析耗时、指令执行耗时。如果配置了超时控制和失败重试次数都将影响整个采集耗时。
            </Message>
          </Col>
        </Row>
        <Row wrap>
          <Col l="24">
            <div id="gateway-topology" style={{width: '600px', height: '500px', position: 'relative'}}>
            </div>
          </Col>
        </Row>
      </div>
    );
  }

  componentDidMount() {
    this.fetch(this.updateTopo.bind(this));
  }

  fetch = (updateTopo) => {
    let that = this;
    ApiOfGatewayDetail(this.props.gateway).then(response => {

      let gatewayNode = {
        type: 'gateway',
        code: response.data.code,
        name: response.data.name,
        connType: response.data.physicalConnType,
        config: response.data.config,
      }
      let deviceNodes = response.data.resources.map( r => {
        return {
          type: 'device',
          code: r.code,
          name: r.name,
          protocol: r.metricGroupSize > 0 ? r.metricGroups[0].protocol : '',
          metricSize: r.metricGroupSize > 0 ? (r.metricGroups[0].metricSize) : 0,
        }
      })
      let data = {}
      data.nodes = [gatewayNode, ...deviceNodes]
      data.links = response.data.resources.map( r => {
        return {
          source: response.data.code,
          target: r.code,
        }
      })

      that.setState({
        data
      })
      updateTopo()

      let report = response.data.resources.map( r => {
        let item = {
          code: r.code,
          name: r.name,
          protocol: r.collectedMetricGroup ? r.collectedMetricGroup.protocol : '',
          metricSize: r.collectedMetricGroup ? r.collectedMetricGroup.metricSize : 0,
          executionCycle: r.executionCycle,
          willMergedSize: '-',
          lastCollectedTime: '-',
          elapsedTime: '-'
        }
        if(r.metricGroupSize > 0 && r.metricGroups[0].metricSize > 0 && r.metricGroups[0].protocol.indexOf('MODBUS') > -1) {
          item.willMergedSize = new Set(r.metricGroups[0].metrics.map(m=>m.command)).size
        }
        return item;
      })
      that.updateReport(report);
    })
  }

  updateReport(report) {
    let that = this;
    that.setState({ report })
    axios
    .all(report.map(item => axios.get(UrlOfRealtimeMonitorData(item.code))))
    .then(axios.spread(function () {
      let totalElapsedTime = 0;
      for (let i = 0; i < arguments.length; i++) {
        let res = arguments[i].data.data
        let resourceCode = res['code']
        let lastCollectedTime = '-', elapsedTime = '-';
        if(res['collectedTime']) {
          lastCollectedTime = moment(res['collectedTime']).format('MM-DD HH:mm:ss')
        }
        if(res['elapsedTime']) {
          elapsedTime = moment.duration(res['elapsedTime']).asSeconds()
          totalElapsedTime += elapsedTime
        }

        let resource = report.find(item => item.code == resourceCode);
        if(resource) {
          resource.lastCollectedTime = lastCollectedTime;
          resource.elapsedTime = elapsedTime;
        }
      }
      report.push({elapsedTime: totalElapsedTime.toFixed(2)})
      that.setState({ report })
    }));
  }

  updateTopo() {
    let data = this.state.data
    const links = data.links.map(d => Object.create(d));
    const nodes = data.nodes.map(d => Object.create(d));

    const simulation = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(links).id(d => d.code).distance(d=> 140))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2));

    const svg = d3.select('#gateway-topology')
        .append('svg')
        .attr("class","svg-canvas")
        .attr('width', width)
        .attr('height', height);

    const tooltip = d3.select('#gateway-topology')
        .append("div")
        .attr("class","tooltip")
        .style("opacity",0.0);

    const link = svg.append("g")
        .attr("stroke", "#C0C0C0")
        .attr("stroke-opacity", 1)
        .selectAll("line")
        .data(links)
        .join("line")
        .attr("stroke-width", d => Math.sqrt(d.value));

    const _g_nodes = svg.append("g")
        .attr('class', 'g-node')
        .attr("stroke", "#fff")
        .attr("stroke-width", 1.5)
        .selectAll("image")
        .data(nodes)
        .join("image")
        .style("width",function(d){
          var width = nodeBaseWidth;
          if(d.type == 'gateway') {
            width = 1.5 * width;
          } else if(d.type == 'device') {
            width = 0.8 * width;
          }
          return width;
        })
        .style("height",function(d){
            var height = nodeBaseHeight;
            if(d.type == 'gateway') {
              height = 1.5 * height;
            } else if(d.type == 'device') {
              height = 0.8 * height;
            }
            return height;
        })
        .attr("href",function(d){
          if(d.type == 'gateway') {
            return gatewayIcon
          } else if(d.type == 'device') {
            return deviceIcon
          }
        })
        .on("mouseover",function(d,i){
            if(d.type == 'device') {
              tooltip.html('名称：' + d.name + '<br />' + '编码：' + d.code + '<br />' + '协议：' +  d.protocol + '<br />' + '指标数：' +  d.metricSize)
              .style("left",(d3.event.offsetX)+"px")
              .style("top",(d3.event.offsetY)+"px")
              .style("display", 'block')
              .style("opacity",1.0);
            } else {
              tooltip.html('名称：' + d.name + '<br />' + '编码：' + d.code + '<br />' + '类型：' +  d.connType + '<br />' + '配置：' +  d.config)
              .style("left",(d3.event.offsetX)+"px")
              .style("top",(d3.event.offsetY)+"px")
              .style("display", 'block')
              .style("opacity",1.0);
            }
        })
        .on("mouseout",function(d,i){
            tooltip.style("display", 'none');
        })
        .call(drag(simulation));

    const _g_texts = svg.select("g.g-node")
        .selectAll("text")
        .data(nodes)
        .join("text")
        .style('stroke',function(d){
          if(d.type == 'gateway'){
            return '#333333';
          }else{
            return '#666666';
          }
        })
        .attr("stroke-width", 1)
        .text(d => d.name);

    simulation.on("tick", () => {
        link
            .attr("x1", d => d.source.x)
            .attr("y1", d => d.source.y)
            .attr("x2", d => d.target.x)
            .attr("y2", d => d.target.y);

        _g_nodes
            .attr("x", d => {
              let offset = 0;
              if(d.type == 'gateway') {
                offset = 1.5 * nodeBaseWidth / 2;
              } else if(d.type == 'device') {
                offset = 0.8 * nodeBaseWidth / 2;
              }
              return d.x - offset
            })
            .attr("y", d => {
              let offset = 0;
              if(d.type == 'gateway') {
                offset = 1.5 * nodeBaseHeight / 2;
              } else if(d.type == 'device') {
                offset = 0.8 * nodeBaseHeight / 2;
              }
              return d.y - offset
            });

        _g_texts
            .attr("x", d => {
              let offset = 0;
              if(d.type == 'gateway') {
                offset = 1.5 * nodeBaseWidth / 2;
              } else if(d.type == 'device') {
                offset = 0.8 * nodeBaseWidth / 2;
              }
              return d.x + offset + 10
            })
            .attr("y", d => d.y);
    })
  }
}